-module(chat_router).
-export([start/0,
	 online_registry/1, 
	 timeout_registry/1,
	 send_message_all_users/3,
	 get_all/0,
	 push_data_handler/2,
	 start_push_data_handler/1]).

% Start all processes used by this module.
start() ->
	register(online_registry, spawn(chat_router, online_registry, [[]])),
	register(timeout_registry, spawn(chat_router, timeout_registry, [[]])).
	

% This function is called from the out(A) of the chat_controller.yaws file
% It will store the Name on the page registry and then execute the push_data_handler
start_push_data_handler(Name) ->
	io:format("Start push data~n", []),
	HandlerPid = self(),
	spawn(fun() -> 
		online_registry ! {add, Name, self()}, 
		push_data_handler(HandlerPid, Name) 
	end),
	[{header, {cache_control, "no-cache"}},
	{streamcontent, "text/plain", ""}].
     

% This function is on charge of receiving the messages sent by a user and send it to every user
% through the stream_chunk_deliver function once the message to send has been created.
% When it has received the message, it removes the pages from the registry.
push_data_handler(HandlerPid, Name) ->
	receive
			% If the user has just logged into the chat, we send him a message with all the users online and we send
			% a message to all the users online indicating that a new user has logged in
		not_timed_out ->
			io:format("Not timed out~n",[]),
			send_message_all_users(2, null, Name),
			UserList = get_all(),
			Message = build_message(4, null, UserList),
			io:format("Not timed out message ~s~n", [Message]),
			yaws_api:stream_chunk_deliver(HandlerPid, Message),
			yaws_api:stream_chunk_end(HandlerPid),
			online_registry ! {remove, {name, Name}};
		timed_out ->
			io:format("Yes timed out~n", []),
			receive
				Message -> 
					io:format("Going to deliver a chunk~n",[]),
					yaws_api:stream_chunk_deliver(HandlerPid, Message),
					yaws_api:stream_chunk_end(HandlerPid),
					online_registry ! {remove, {name, Name}}
			after 30000 ->
				yaws_api:stream_chunk_deliver(HandlerPid, " timeout"),
				yaws_api:stream_chunk_end(HandlerPid),
				online_registry ! {remove, {name, Name}}
			end;
		refreshed ->
			UserList = get_all(),
			Message = build_message(4, null, UserList),
			io:format("Not timed out message ~s~n", [Message]),
			yaws_api:stream_chunk_deliver(HandlerPid, Message),
			yaws_api:stream_chunk_end(HandlerPid),
			online_registry ! {remove, {name, Name}}
	end.
    

% Each user is mapped with his Name and the PID of its handler.
% This function keeps track of all this information. 
online_registry(NameList) ->
	io:format("Online registry created~n", []),
	receive
		{add, Name, Pid} ->
			io:format("Online registry add~n", []),
			NewList = online_registry_add(Name, Pid, NameList),
			io:format("Online registry: ~p~n", [NewList]),
			online_registry(NewList);
		{remove, {name, Name}} ->
			io:format("Online registry remove~n", []),
			NewList = online_registry_remove_by_name(Name, NameList),
			io:format("Online registry: ~p~n", [NewList]),
			online_registry(NewList);
		{get, From, Name} ->
			online_registry_get(From, Name, NameList),
			online_registry(NameList);
		{get_all, From} ->
			online_registry_get_all(From, NameList),
			online_registry(NameList)
	end.


online_registry_add(Name, Pid, NameList) ->
	case lists:keysearch(Name, 1, NameList) of
		{value, {_, _}} ->
			Pid ! refreshed,
			timeout_registry ! {add, Name, Pid},
			NameList;
		false ->
			timeout_registry ! {alive, Pid, Name},
			[{Name, Pid} | NameList]
	end.

% When removing a user from the online_registry, we add it to the timeout_registry
% and create a remove_definitely process
online_registry_remove_by_name(Name, NameList) ->
	case lists:keysearch(Name, 1, NameList) of
		{value, {_, _}} ->
			NewList = lists:keydelete(Name, 1, NameList),
			spawn(fun() -> 
				timeout_registry ! {add, Name, self()}, 
				remove_definitely(Name) 
			end),
			NewList;
		false ->
			NameList
	end.

online_registry_get(From, Name, NameList) ->
	case lists:keysearch(Name, 1, NameList) of
		{value, {_, Pid}} -> 
			From ! {result, {Name, Pid}};
		false -> From ! {result, false}
	end.

online_registry_get_all(From, NameList) ->
    From ! {result, NameList}.
    
    
% Each user that was timed out is mapped with his Name and the PID of its handler.
% This function keeps track of all this information.
timeout_registry(NameList) -> 
	io:format("Timedout registry created~n", []),
	receive
		{add, Name, Pid} ->
			io:format("Timedout registry add~n", []),
			NewList = timeout_registry_add(Name, Pid, NameList),
			io:format("Timedout registry: ~p~n", [NewList]),
			timeout_registry(NewList);
		{remove, {name, Name}} ->
			io:format("Timedout registry remove~n", []),
			NewList = timeout_registry_remove_by_name(Name, NameList),
			io:format("Timedout registry: ~p~n", [NewList]),
			timeout_registry(NewList);
		{remove_definitely, {name, Name}} ->
			io:format("Timedout registry remove definitely~n", []),
			timeout_registry_remove_definitely(Name, NameList),
			timeout_registry(NameList);
		{alive, From, Name} ->
			io:format("Timedout registry alive~n", []),
			timeout_registry_alive(From, Name, NameList),
			timeout_registry(NameList)
	end.


timeout_registry_add(Name, Pid, NameList) ->
	NewList = case lists:keysearch(Name, 1, NameList) of
		{value, {_, _}} ->
			lists:keydelete(Name, 1, NameList);
		false ->
			NameList
	end,
	[{Name, Pid} | NewList].
			

timeout_registry_remove_by_name(Name, NameList) ->
	lists:keydelete(Name, 1, NameList).
	
timeout_registry_remove_definitely(Name, NameList) ->
	case lists:keysearch(Name, 1, NameList) of
		{value, {_, _}} ->
			send_message_all_users(3, null, Name),
			timeout_registry ! {remove, {name, Name}};
		false ->
			true
	end.

timeout_registry_alive(From, Name, NameList) ->
	case lists:keysearch(Name, 1, NameList) of
		{value, {_, _}} ->
			timeout_registry ! {remove, {name, Name}},
			From ! timed_out;
		false ->
			From ! not_timed_out
	end.
	

% This function is called when a User has timed out and then he is stored in the timeout_registry
% It just waits 10 seconds to remove the user from the timeout_registry
% If the user is still online, a new request will arrive before those 10 seconds and it will be detected
% that this user was online
remove_definitely(Name) ->
	io:format("Remove definitely created~n", []),
	receive
	after 20000 ->
		timeout_registry ! {remove_definitely, {name, Name}}
	end.



% This function sends a message to the Name.
% This message will be treated in the push_data_handler function
send(Name, Message) ->
	online_registry ! {get, self(), Name},
	receive
		{result, {_, Pid}} ->
			Pid ! Message
	end.
	

% This function obtains a list of all the users that are at the moment in the chat
% Then, it sends the message to each user
send_message_all_users(Kind, Message, User) ->
	Mess = build_message(Kind, Message, User),
	L = [Name || {Name, _} <- get_all()],
	lists:map(fun(N) -> send(N, Mess) end, L).


% Function that builds the different types of messages
build_message(1, Message, User) ->
	Mess = string:concat("1-", atom_to_list(User)),
	Mess2 = string:concat(Mess, ":"),
	string:concat(Mess2, Message);
	
build_message(4, _, UserList) ->
	List = lists:foldl(fun({X,_},Acc) -> string:concat(Acc, string:concat(":", atom_to_list(X))) end, "", UserList),
	string:concat("4-", List);

build_message(Kind, _, User) ->
	[Num] = io_lib:format("~w", [Kind]),
	Mess = string:concat(Num, "-"),
	string:concat(Mess, atom_to_list(User)).

	    
% Returns a list of {Name,Pid} for all active pages.
get_all() ->
	online_registry ! {get_all, self()},
	receive 
		{result, X} -> X
	end.

